package com.ccrfid.rmc6.lbservice3.manager;

import com.ccrfid.rmc6.appconfig.websocket.MsgEntity;
import com.ccrfid.rmc6.entity.alarm.AlarmNotifyRecordEntity;
import com.ccrfid.rmc6.entity.alarm.AlarmRecordEntity;
import com.ccrfid.rmc6.entity.device.CardEntity;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.entity.sys.AccountEntity;
import com.ccrfid.rmc6.entity.sys.DictEntity;
import com.ccrfid.rmc6.lbservice3.client.WebClient;
import com.ccrfid.rmc6.lbservice3.pojo.count.PersonnelCount;
import com.ccrfid.rmc6.lbservice3.pojo.count.PersonnelDetailCount;
import com.ccrfid.rmc6.lbservice3.pojo.evt.LocationPersonnelChange;
import com.ccrfid.rmc6.lbservice3.pojo.locate.PersonnelLocateInfo;
import com.ccrfid.rmc6.lbservice3.pojo.locate.PersonnelTrackInfo;
import com.ccrfid.rmc6.lbservice3.pojo.msg.MsgLocate;
import com.ccrfid.rmc6.pojo.Constants;
import com.ccrfid.rmc6.pojo.dto.AlarmRecordForAppDto;
import com.ccrfid.rmc6.pojo.dto.alarm.AlarmNotifyRecordDto;
import com.ccrfid.rmc6.pojo.dto.prison.door.InOutDoorMsg;
import com.ccrfid.rmc6.service.alarm.AlarmRecordService;
import com.ccrfid.rmc6.service.device.CardService;
import com.ccrfid.rmc6.service.personnel.PersonnelService;
import com.ccrfid.rmc6.service.poi.PoiService;
import com.ccrfid.rmc6.service.sys.AccountService;
import com.ccrfid.rmc6.service.sys.DictService;
import com.ccrfid.rmc6.service.sys.OrgService;
import com.ccrfid.rmc6.ws.c5.cardreader.pojo.UploadCardRequest;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class ClientManager {
    private static final Logger logger = LoggerFactory.getLogger(ClientManager.class);
    @Autowired
    private EventBus eventBus;
    @Autowired
    private AccountService accountService;
    @Autowired
    private DictService dictService;
    @Autowired
    private PersonnelService personnelService;
    @Autowired
    private PoiService poiService;
    @Autowired
    private LocationManager locationManager;
    @Autowired
    private OrgService orgService;

    @Autowired
    private CardService cardService;
    @Autowired
    private AlarmRecordService alarmRecordService;

    private Map<String, WebClient> webClientMap;        //key:clientId
    private Multimap<Long, String> trackPersonnelMap;   //key:personnelId, value:set<clientId>

    public ClientManager() {
        this.webClientMap = new ConcurrentHashMap<>();
        this.trackPersonnelMap = MultimapBuilder.hashKeys().hashSetValues().build();
    }

    @PostConstruct
    private void init() {
        eventBus.register(this);
    }

    @Subscribe
    public void onPersonnelTrack(PersonnelLocateInfo locateInfo) {
        if (trackPersonnelMap.containsKey(locateInfo.getPersonnelId())) {
            trackPersonnelMap.get(locateInfo.getPersonnelId())
                    .forEach(clientId -> {
                        //人员追踪
                        MsgLocate msgLocate = locateInfo.getMsgLocate();
                        PersonnelTrackInfo trackInfo =
                                new PersonnelTrackInfo(locateInfo.getPersonnelId(), msgLocate.getCurrentLocator(),
                                        msgLocate.getCurrentPos(), msgLocate.getCurrentFloorNumber(), msgLocate.getTime());
                        PoiLocationEntity locationEntity =
                                poiService.get3LevelLocationByPos(msgLocate.getCurrentFloorNumber(), msgLocate.getCurrentPos());
                        if (locationEntity != null) {
                            trackInfo.setLocationId(locationEntity.getId());
                            trackInfo.setLocationName(locationEntity.getLocationName());
                        }
                        WebClient webClient = webClientMap.get(clientId);
                        if (webClient != null) {
                            try {
                                webClient.postMsg(new MsgEntity<>("personnelTrack", trackInfo));
                            } catch (IOException ex) {
                                ex.printStackTrace();
                                webClientMap.remove(clientId);
                                webClient.closeConnection();
                                logger.info("delete & close webClient: {}", clientId);
                            }
                        }
                    });
        }
    }

    @Subscribe
    public void onAlarmNotifyRecordDto(AlarmNotifyRecordDto notifyRecordDto) {
        long startTime = System.currentTimeMillis();
        if (logger.isTraceEnabled()) {
            logger.trace("onAlarmNotifyRecordDto: {}", notifyRecordDto);
        }
        AlarmNotifyRecordEntity notifyRecordEntity = notifyRecordDto.getAlarmNotifyRecord();
        List<AccountEntity> accountList = accountService.getAccountListByPersonnelId(notifyRecordEntity.getNotifyPersonnelId());

        if (accountList != null && accountList.size() > 0) {
            for (AccountEntity accountEntity : accountList) {
                postMsg2Account(accountEntity.getUserAccount(), notifyRecordDto.getAlarmRecord());

                postAlarmMsgToApp(accountEntity.getUserAccount(), notifyRecordEntity.getNotifyPersonnelId());
            }
        }
        long stopTime = System.currentTimeMillis();
        if (logger.isTraceEnabled()) {
            logger.trace("onAlarmNotifyRecordDto: {}, cost: {}ms", notifyRecordDto, stopTime - startTime);
        }
    }

    private void postMsg2Account(String account, AlarmRecordEntity alarmRecord) {
        //向特定账号连接的客户端推送告警信息
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (account.equals(webClient.getUserAccount())) {
                try {
                    webClient.postMsg(new MsgEntity<>("alarm", alarmRecord));
                } catch (IOException ex) {
                    ex.printStackTrace();
                    iterator.remove();
                    webClient.closeConnection();
                    logger.info("delete & close webClient: {}", webClient.getClientId());
                }
            }
        }
    }

    @Subscribe
    public void onLocationPersonnelChange(LocationPersonnelChange locationPersonnelChange) {
        long startTime = System.currentTimeMillis();
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (webClient.getLocationIdSet().contains(locationPersonnelChange.getLocationId())) {
                try {
                    webClient.postMsg(new MsgEntity<>("location", getPersonnelCountMsg(locationPersonnelChange)));
                } catch (IOException ex) {
                    ex.printStackTrace();
                    iterator.remove();
                    webClient.closeConnection();
                    logger.info("delete & close webClient: {}", webClient.getClientId());
                }
            }
        }
        long stopTime = System.currentTimeMillis();
        if (logger.isTraceEnabled()) {
            logger.trace("onLocationPersonnelChange cost: {} ms, location: {}, personnels: {}",
                    stopTime - startTime, locationPersonnelChange.getLocationId(), locationPersonnelChange.getPersonnelIdSet());
        }
    }

    private PersonnelCount getPersonnelCountMsg(LocationPersonnelChange locationPersonnelChange) {
        PersonnelCount personnelCount = new PersonnelCount();
        personnelCount.setLocationId(locationPersonnelChange.getLocationId());
        personnelCount.setCount((long) locationPersonnelChange.getPersonnelIdSet().size());

        List<PersonnelDetailCount> list = new ArrayList<>();
        DictEntity prisonerType = dictService.getDict(Constants.PERSONNEL_TYPE.PRISONER.id);
        Long prisonerCount = personnelService.countByIdListAndPersonnelType(locationPersonnelChange.getPersonnelIdSet(), Constants.PERSONNEL_TYPE.PRISONER.id);
        list.add(new PersonnelDetailCount(Constants.PERSONNEL_TYPE.PRISONER.id, prisonerType.getDictName(), prisonerCount));

        DictEntity workerType = dictService.getDict(Constants.PERSONNEL_TYPE.WORKER.id);
        Long workerCount = personnelService.countByIdListAndPersonnelType(locationPersonnelChange.getPersonnelIdSet(), Constants.PERSONNEL_TYPE.WORKER.id);
        list.add(new PersonnelDetailCount(Constants.PERSONNEL_TYPE.WORKER.id, workerType.getDictName(), workerCount));

        DictEntity visitorType = dictService.getDict(Constants.PERSONNEL_TYPE.VISITOR.id);
        Long visitorCount = personnelService.countByIdListAndPersonnelType(locationPersonnelChange.getPersonnelIdSet(), Constants.PERSONNEL_TYPE.VISITOR.id);
        list.add(new PersonnelDetailCount(Constants.PERSONNEL_TYPE.VISITOR.id, visitorType.getDictName(), visitorCount));

        personnelCount.setDetail(list);

        return personnelCount;
    }

    //更新某个客户端浏览器的可视范围
    public void updateWebClientViewRange(String clientId, List<PoiLocationEntity> locationEntities) {
        WebClient webClient = webClientMap.get(clientId);
        if (webClient != null) {
            Set<Long> locationIdSet = new HashSet<>();
            for (PoiLocationEntity locationEntity : locationEntities) {
                locationIdSet.add(locationEntity.getId());
            }
            webClient.updateLocations(locationIdSet);
            //发送区域初始统计数据
            for (Long locationId : locationIdSet) {
                try {
                    webClient.postMsg(new MsgEntity<>("location",
                            getPersonnelCountMsg(locationManager.getLocationPersonnelInfo(locationId))));
                } catch (IOException ex) {
                    ex.printStackTrace();
                    webClientMap.remove(clientId);
                    webClient.closeConnection();
                    logger.info("delete & close webClient: {}", clientId);
                    break;
                }
            }
        }
    }

    public void updateWebClientViewRange(String clientId, Long locationId) {
        WebClient webClient = webClientMap.get(clientId);
        if (webClient != null) {
            Set<Long> locationIdSet = new HashSet<>();
            locationIdSet.add(locationId);
            webClient.updateLocations(locationIdSet);
            //发送区域初始统计数据
            try {
                webClient.postMsg(new MsgEntity<>("location",
                        getPersonnelCountMsg(locationManager.getLocationPersonnelInfo(locationId))));
            } catch (IOException ex) {
                ex.printStackTrace();
                webClientMap.remove(clientId);
                webClient.closeConnection();
                logger.info("delete & close webClient: {}", clientId);
            }
        }
    }

    public WebClient getWebClient(String clientId) {
        return webClientMap.get(clientId);
    }

    public void addWebClient(WebClient webClient) {
        webClientMap.put(webClient.getClientId(), webClient);
    }

    public void deleteWebClient(String clientId) {
        if (webClientMap.containsKey(clientId)) {
            WebClient webClient = webClientMap.get(clientId);
            webClient.closeConnection();

            webClientMap.remove(clientId);
        }
    }

    public void addTrack(Long personnelId, String clientId) {
        logger.debug("addPersonnelTrack, personnelId: {}, clientId: {}", personnelId, clientId);
        trackPersonnelMap.put(personnelId, clientId);
    }

    public void deleteTrack(Long personnelId, String clientId) {
        logger.debug("deletePersonnelTrack, personnelId: {}, clientId: {}", personnelId, clientId);
        trackPersonnelMap.remove(personnelId, clientId);
    }

    public boolean postReadCardMsg(String ipAddr, CardEntity cardEntity) {
        boolean result = false;
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (webClient.getClientIpAddrs().contains(ipAddr)) {
                try {
                    webClient.postMsg(new MsgEntity<>("readCard", cardEntity));
                    result = true;
                } catch (IOException ex) {
                    webClient.closeConnection();
                    iterator.remove();
                }
            }
        }
        logger.debug("postReadCardMsg to {} - {}", ipAddr, result);
        return result;
    }

    public boolean postCardReaderNotFoundMsg(String ipAddr, UploadCardRequest uploadCardRequest) {
        boolean result = false;
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (webClient.getClientIpAddrs().contains(ipAddr)) {
                try {
                    webClient.postMsg(new MsgEntity<>("cardReaderNotFound", uploadCardRequest.getCardReaderNumber()));
                    result = true;
                } catch (IOException ex) {
                    webClient.closeConnection();
                    iterator.remove();
                }
            }
        }
        logger.debug("post cardReaderNotFound msg to {} - {}", ipAddr, result);
        return result;
    }

    public Map<String, WebClient> getWebClientMap() {
        return webClientMap;
    }

    //更新客户端关注的二道门
    public void updateDoorSubscription(String clientId, Long doorId) {
        WebClient webClient = webClientMap.get(clientId);
        if (webClient != null) {
            webClient.setDoorId(doorId);
        }
    }

    @Subscribe
    public void onInOutDoorMsg(InOutDoorMsg inOutDoorMsg) {
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (webClient.getDoorId() != null && webClient.getDoorId().equals(inOutDoorMsg.getDoorId())) {
                try {
                    webClient.postMsg(new MsgEntity<>("door-msg", inOutDoorMsg));
                } catch (IOException ex) {
                    ex.printStackTrace();
                    iterator.remove();
                    webClient.closeConnection();
                    logger.info("delete & close webClient: {}", webClient.getClientId());
                }
            }
        }
    }

    public void postAlarmMsgToApp(String account, Long notifyPersonnelId) {
        //向特定账号连接的客户端推送告警信息
        Iterator<Map.Entry<String, WebClient>> iterator = webClientMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebClient> entry = iterator.next();
            WebClient webClient = entry.getValue();
            if (account.equals(webClient.getUserAccount())) {
                try {
                    webClient.postMsg(new MsgEntity<>("alarmRecordChanged", notifyPersonnelId));
                } catch (IOException ex) {
                    ex.printStackTrace();
                    iterator.remove();
                    webClient.closeConnection();
                    logger.info("delete & close webClient: {}", webClient.getClientId());
                }
            }
        }
    }

    public List<AlarmRecordForAppDto> getAlarmMsgToAppList(Long notifyPersonnelId) {
        List<AlarmRecordForAppDto> appDtoList = new ArrayList<>();

        List<Integer> alarmTypeList = new ArrayList<>();
        List<DictEntity> dictEntityList = dictService.getGuardTypes();
        for (DictEntity dictEntity : dictEntityList) {
            alarmTypeList.add(dictEntity.getId());
        }
        int violation = alarmRecordService.getViolation(notifyPersonnelId, alarmTypeList, 0);
        List<AccountEntity> accountEntityList = accountService.getAccountListByPersonnelId(notifyPersonnelId);
        for (AccountEntity accountEntity : accountEntityList) {
            Integer orgId = accountEntity.getOrgId();
            List<Long> orgIdList = orgService.getSubOrgIdList(Long.valueOf(orgId));
            int lowPowerCount = cardService.getLowpower(1, 1, 1, 101, orgIdList);
            int cutOffCount = cardService.getCutOff(1, 1, 1, 101, orgIdList);
            int offLineCount = cardService.getOffline(1, 0, 1, 101, orgIdList);

            AlarmRecordForAppDto alarmRecordForAppDto = new AlarmRecordForAppDto();
            alarmRecordForAppDto.setNotifyPersonnelId(accountEntity.getId());
            alarmRecordForAppDto.setCallCount(0);
            alarmRecordForAppDto.setLowPowerCount(lowPowerCount);
            alarmRecordForAppDto.setCutoffCount(cutOffCount);
            alarmRecordForAppDto.setOffLineCount(offLineCount);
            alarmRecordForAppDto.setViolationCount(violation);
            appDtoList.add(alarmRecordForAppDto);
        }
        return appDtoList;
    }

}
